package hydrap2p.hubmanager;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.thrift.TException;

import hydrap2p.ServiceLocation;
import hydrap2p.helper.Pinger;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.hub.DuplicateProtocol;
import hydrap2p.hub.HubProtocol;
import hydrap2p.hub.HubStatus;
import hydrap2p.hub.NameNotFound;
import hydrap2p.hub.NameTaken;
import hydrap2p.hub.HubManager;
import hydrap2p.hub.ProtocolNotConnected;

public class Server implements HubManager.Iface {

	private final Listeners listeners;
	private final Map<String, ServiceLocation> protocols;
	private final Map<String, HubStatus> statuses;
	
	public Server()
	{
		listeners = new Listeners();
		protocols = new TreeMap<String, ServiceLocation>();
		statuses = new TreeMap<String, HubStatus>();
		List<HubStatus> old=Persistance.load();
		for (HubStatus temp : old)
		{
			statuses.put(getConnName(temp.hubtype,temp.name), temp);
		}
		new MyPinger("ServiceManager", ServiceConnector.getLocation("ServiceManager"));
	}
	
	private String getConnName(String hubtype, String name)
	{
		return name+"@"+hubtype;
	}
	
	@Override
	public void addConnection(String hubtype, String name, Map<String, String> params, boolean autoconnect)
			throws NameTaken {
		synchronized(statuses)
		{
			if (statuses.containsKey(getConnName(hubtype, name)))
				throw new NameTaken(hubtype,name);
			
			HubStatus temp = new HubStatus();
			temp.setHubtype(hubtype);
			temp.setName(name);
			temp.setParams(params);
			temp.autoconnect=autoconnect;
			temp.setConnected(false);
			temp.setStatus("");
			
			statuses.put(getConnName(hubtype, name), temp);
			listeners.connectionAdded(temp);

			Persistance.set(getStatuses());
			Persistance.save();
		}
		if (autoconnect)
		{
			try {
				connect(hubtype, name);
			} catch (NameNotFound e) {
				// Ignored during startup
			} catch (ProtocolNotConnected e) {
				// Ignored during startup
			}
		}
	}

	@Override
	public void changeConnection(String hubtype, String name, Map<String, String> params, boolean autoconnect) throws NameNotFound {
		boolean shouldconnect=false;
		synchronized(statuses)
		{
			HubStatus temp = statuses.get(getConnName(hubtype, name));
			if (temp!=null)
			{
				shouldconnect=autoconnect && (autoconnect!=temp.autoconnect);
				temp.params=params;
				temp.autoconnect=autoconnect;
				listeners.connectionChanged(temp);

				Persistance.set(getStatuses());
				Persistance.save();
			}
			else
				throw new NameNotFound(hubtype, name);
		}
		if (shouldconnect)
		{
			try {
				connect(hubtype, name);
			} catch (NameNotFound e) {
				// Ignored during first start
			} catch (ProtocolNotConnected e) {
				// Ignored during first start
			}
		}
	}

	@Override
	public void removeConnection(String hubtype, String name) {
		synchronized(statuses)
		{
			HubStatus temp = statuses.remove(getConnName(hubtype, name));
			if (temp!=null)
				listeners.connectionRemoved(temp);
		}
	}

	@Override
	public HubStatus getStatus(String hubtype, String name) {
		synchronized(statuses)
		{
			HubStatus temp = statuses.get(getConnName(hubtype, name));
			return temp;
		}
	}

	@Override
	public List<HubStatus> getStatuses() {
		synchronized(statuses)
		{
			return new LinkedList<HubStatus>(statuses.values());
		}
	}

	@Override
	public List<String> getProtocols() {
		synchronized(protocols)
		{
			return new LinkedList<String>(protocols.keySet());
		}
	}

	@Override
	public void connect(String hubtype, String name) throws NameNotFound, ProtocolNotConnected {
		ServiceLocation protocol;
		synchronized(protocols)
		{
			protocol = protocols.get(hubtype);
		}
		if (protocol!=null)
		{
			HubProtocol.Iface connection = ServiceConnector.getService(HubProtocol.Client.class, protocol);
			HubStatus status = getStatus(hubtype, name);
			if (connection==null)
			{
				unregisterProtocol(hubtype);
				throw new ProtocolNotConnected(hubtype, name);
			}
			if (status==null)
			{
				throw new NameNotFound(hubtype, name);
			}
			if (connection!=null && status!=null)
			{
				try {
					connection.connect(name, status.params);
				} catch (TException e) {
					unregisterProtocol(hubtype);
				}
			}
		}
		else
		{
			throw new ProtocolNotConnected(hubtype, name);
		}
		
	}

	@Override
	public void disconnect(String hubtype, String name) throws TException {
		ServiceLocation protocol;
		synchronized(protocols)
		{
			protocol = protocols.get(hubtype);
		}
		if (protocol!=null)
		{
			HubProtocol.Iface connection = ServiceConnector.getService(HubProtocol.Client.class, protocol);
			HubStatus status = getStatus(hubtype, name);
			if (connection!=null && status!=null)
			{
				try {
					connection.disconnect(name);
				} catch (TException e) {
					unregisterProtocol(hubtype);
				}
			}
		}
	}

	@Override
	public void registerProtocol(String name, ServiceLocation location)	throws DuplicateProtocol {
		synchronized(protocols)
		{
			if (protocols.containsKey(name))
				throw new DuplicateProtocol(name); 
			protocols.put(name, location);
			listeners.protocolRegistered(name);
		}
		List<HubStatus> list=new LinkedList<HubStatus>();
		synchronized(statuses)
		{
			for (HubStatus status : statuses.values())
			{
				if (status.autoconnect && !status.connected)
				{
					list.add(status);
				}
			}
		}
		for (HubStatus status : list)
		{
			try {
				connect(status.hubtype, status.name);
			} catch (NameNotFound e) {
				// Strange
			} catch (ProtocolNotConnected e) {
				// Doubtful, as this is the protocol we just connected
			}
		}
	}

	@Override
	public void unregisterProtocol(String name) {
		synchronized(statuses)
		{
			for (HubStatus status : statuses.values())
			{
				status.connected=false;
				status.status=name+" shut down";
				listeners.connectionConnected(status);
			}
		}
		synchronized(protocols)
		{
			protocols.remove(name);
			listeners.protocolUnregistered(name);
		}
	}

	@Override
	public void connected(String hubtype, String name, boolean connected, String status)
			throws TException {
		synchronized(statuses)
		{
			HubStatus temp = statuses.get(getConnName(hubtype, name));
			temp.connected=connected;
			temp.status=status;
			listeners.connectionConnected(temp);
		}
	}

	@Override
	public List<String> supportedInterfaces() throws TException {
		return Arrays.asList("Service", "HubManager");
	}

	@Override
	public void ping(){
	}

	private class MyPinger extends Pinger
	{
		public MyPinger(String name, ServiceLocation location) {
			super(name, location);
		}

		@Override
		public void failure(String name) {
			HubManagerMain.shutdown();
		}
		
	}
}
